package wiiusej.motionAlgorithms;

import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Vector;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;

import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.Priority;
import org.apache.log4j.PropertyConfigurator;

import wiiusej.apptest.statistics.AccelerationPaquet;
import wiiusej.apptest.statistics.AccelerationPaquetList;

public class Kmeans implements Callable{

	//Logger
	private static Logger fileLogger;
	private static Logger performanceLogger;
	private static final String fileURL = 
		"src/test/java/wiiusej/apptest/statistics/Kmeans/KmeansPerformance";
	
	private static long id; 
	
	//CENTROIDS - defined by heuristics by taking motion samples
	private CentroidsEnum centroidsType;
	private Vector<Centroid> centroids;
	
	//COM queues
	private BlockingQueue<Vector<AccelerationPaquet>> comQueue;
	private Vector<AccelerationPaquet> myEventVector;
	
	//FACTORS
	private final int PACKET_FACTOR = 50;
	private final double MILLI_FACTOR = 1.0E06;
	
	//OTHERS
	private String pattern = "000.00";
	DecimalFormat myFormatter = new DecimalFormat(pattern);

	public Kmeans(Vector<AccelerationPaquet> myEventVector, Vector<Centroid> centroids, long wiimoteId){
		//this.comQueue = comQueue;
		
		this.myEventVector = myEventVector;
		this.centroids = centroids;
		this.id = wiimoteId; 
			
			
			
		PropertyConfigurator.configure("log4j.properties");
		fileLogger = Logger.getLogger("kmeans");		
		performanceLogger = Logger.getLogger("kmeansPerformance");
		
		
		PatternLayout pattern1 = 
			new PatternLayout("%m%n");
		
		try{			
			FileAppender performanceApd=new FileAppender(pattern1,fileURL + "_" + id + 
					".log");			
			performanceApd.setThreshold(Priority.DEBUG);		
			performanceLogger.addAppender(performanceApd);			
		}catch(Exception e){
			
		}
		
	}

	public Vector<Centroid> call() throws Exception {

		long start = System.nanoTime();
		Timestamp tsStart = new Timestamp (start);
		SimpleDateFormat tsFormat = new SimpleDateFormat("mm:ss:SSS");
		
		
		fileLogger.debug("***************************************************************************");
		fileLogger.debug("NEW ITERATION");
		fileLogger.debug("***************************************************************************");
		
		fileLogger.debug("");
		fileLogger.debug("Centers:");
		
		for(Centroid cluster : centroids){					
			fileLogger.info(cluster.toString());
			fileLogger.debug(cluster.getMotionSamples());
		}
		
		
		fileLogger.debug("");		
		fileLogger.debug("EventVector Size: " + myEventVector.size());
		
		calculeMotionParameters();
		
		boolean convergence = false;
		
		while(!convergence){
			
			//for counting if all the centers remains the same
			int centerCounter= 0;
			//Motion sample clustering. We put the motionSamples into centroids according with the distance
			clusterMotions();		
			
			//Compute new centers
			fileLogger.debug("");
			fileLogger.debug("NewCenters: ");
			
			for(Centroid cluster : centroids){
				
				cluster.computeNewCenter();
				
				fileLogger.debug(cluster.toString());				
				
				if(cluster.sameCenter()){
					centerCounter++;					
				}
			}
			
			fileLogger.debug("SameCenters: " + centerCounter + " ExistentCenters: " + centroids.size());
			
			if(centerCounter == centroids.size()){
				//all centers remains the same than the last iteration - convergence 
				fileLogger.debug("");
				fileLogger.info("ALGORITHM HAS CONVERGED. Centers:");
				fileLogger.debug("");
				
				for(Centroid cluster : centroids){					
					fileLogger.info(cluster.toString());
					fileLogger.info("ContainedSamples: " + cluster.getMotionSamples().size());
					fileLogger.debug("");
				}
				
				convergence = true;				
			}
			
		}
		
		//myEventVector.clear();
		fileLogger.debug("EventVectorSize: " + myEventVector.size());
		Timestamp tsEnd = new Timestamp (System.currentTimeMillis());
		long end = System.nanoTime();		
		
		
		performanceLogger.info("End: " + tsFormat.format(tsEnd) + " Start: " + tsFormat.format(tsStart)				
				+ " " + myFormatter.format((end-start)/MILLI_FACTOR));
		
		return centroids;		
	}	
	

	private void clusterMotions() {
		
		double distanceToCenter;
		SimpleDateFormat tsFormat = new SimpleDateFormat("mm:ss:SS");
		
		fileLogger.debug("");
		fileLogger.debug("*** CLUSTERING ALGORITHM: ");
		fileLogger.debug("");

		
		fileLogger.info(" 1. Distance to Cluster: ");
		for(Centroid cluster : centroids){
			
			fileLogger.debug("Cluster: " + cluster.getInitType() + 
					" MotionSamplesSize: " + cluster.getMotionSamples().size());
			
			/*
			for(AccelerationPaquet acc: cluster.getMotionSamples()){
				fileLogger.debug(acc.toString());				
			}*/
			
			for(AccelerationPaquet sample : myEventVector){
				
				distanceToCenter = distanceToCluster(cluster, sample);		
				
				/*fileLogger.debug("Distance: " + distanceToCenter+  " FromPacket: " + 
						tsFormat.format(sample.getTime()) + " ToCenter: " + cluster.toString() );*/
				
				if(sample.getDistCenter() == 0 && sample.getCurrentCenter() == null){
					fileLogger.debug("First Center Ever: " + cluster.getInitType());
					sample.setDistCenter(distanceToCenter);
					sample.setCurrentCenter(cluster.getInitType());					
				
				}else if(distanceToCenter < sample.getDistCenter()){
					fileLogger.debug("Closest Centroid: " + cluster.getInitType());
					sample.setDistCenter(distanceToCenter);
					sample.setCurrentCenter(cluster.getInitType());					
				}
				
				fileLogger.debug("");
				
			}
			
		}
		
		//puts the sample into the center data structure
		fileLogger.debug(" 2. Clustering into Centroids: ");
		fileLogger.debug("");		
		
		
		for(Centroid cluster : centroids){
			fileLogger.debug("");
		
			fileLogger.debug("Cluster: " + cluster.getInitType() + 
					" MotionSamplesSize: " + cluster.getMotionSamples().size());
			/*
			for(AccelerationPaquet acc: cluster.getMotionSamples()){
				fileLogger.debug(acc.toString());				
			}*/
			fileLogger.debug("");
			
			for(AccelerationPaquet motionSample : myEventVector){
				
				fileLogger.debug(" MotionSample: " + motionSample.getTime() + " ClosestCenter: " + 
						motionSample.getCurrentCenter());
				
				
				
				if(cluster.getInitType() == motionSample.getCurrentCenter() && 
						!cluster.getMotionSamples().contains(motionSample)){
					
					cluster.addMotionSample(motionSample);
					
					fileLogger.info("Putting Sample: " + tsFormat.format(motionSample.getTime()) + 
							" ToCentroid: " + cluster.getInitType());
					
				}				
			}				
		}		
		
		/*
		for(AccelerationPaquet motionSample : myEventVector){			
			fileLogger.debug("ClusteredInCenter: " + motionSample.getCurrentCenter());
		}*/
		
		
		
	}


	private double distanceToCluster(Centroid cluster,
			AccelerationPaquet sample) {
		SimpleDateFormat tsFormat = new SimpleDateFormat("mm:ss:SS");
		/*
		fileLogger.debug("CentroID " + cluster.toString() + 
				" packetMean: " + cluster.getPacketMeanCenter() + 
				" xVar: " + cluster.getVarXCenter() + 
				" yVar: " + cluster.getVarYCenter() + 
				" zVar: " + cluster.getVarZCenter());
		*/
		
		fileLogger.debug("CentroID: " + cluster.getInitType() + " Packet: " + tsFormat.format(sample.getTime()));
		
		/*
		fileLogger.debug("Sample -- PacketMean: " + sample.getCounter() + 
				" xVar: " + sample.getxVar() + 
				" yVar: " + sample.getyVar() + 
				" zVar: " + sample.getzVar());*/
		
		
		//return sqrt(pow((b.x - a.x),2) + pow((b.y - a.y),2));
		double pDist = Math.pow(((sample.getCounter() - cluster.getPacketMeanCenter())*PACKET_FACTOR), 2);
		double xVarDist = Math.pow((sample.getxVar() - cluster.getVarXCenter()), 2);
		double yVarDist = Math.pow((sample.getyVar() - cluster.getVarYCenter()), 2);
		double zVarDist = Math.pow((sample.getzVar() - cluster.getVarZCenter()), 2);
		double distance = Math.sqrt(xVarDist + yVarDist + zVarDist + pDist);
		
		
		fileLogger.debug("");
		fileLogger.debug("PacketDistance: " + pDist);
		fileLogger.debug(" Xvar: " + xVarDist);
		fileLogger.debug(" Yvar: " + yVarDist);
		fileLogger.debug(" Zvar: " + zVarDist);
		fileLogger.info(" DistanceToCluster: " + distance);
		
		
		return distance;
	}


	private void calculeMotionParameters() {
		
		SimpleDateFormat tsFormat = new SimpleDateFormat("mm:ss:SS");
		
		for(AccelerationPaquet accelerationPacket : myEventVector){
			
			double packetAverage = accelerationPacket.getCounter();
			
			//compute averages 
			double xMean = computeAverage(accelerationPacket.getAccX());
			double yMean = computeAverage(accelerationPacket.getAccY());
			double zMean = computeAverage(accelerationPacket.getAccZ());
			
			accelerationPacket.setMeans(xMean, yMean, zMean);
			
			double xVar = computeVar(xMean, accelerationPacket.getAccX());
			double yVar = computeVar(yMean, accelerationPacket.getAccY());
			double zVar = computeVar(zMean, accelerationPacket.getAccZ());
			
			accelerationPacket.setVariances(xVar, yVar, zVar);
			
			
			fileLogger.debug("New Acceleration Packet Stats -- ChunkPeriod: " + 
					tsFormat.format(accelerationPacket.getTime()) + 
					" Packets:"+ accelerationPacket.getCounter());		
			
			fileLogger.debug(" Xmean: " + xMean + " Xvar: " + accelerationPacket.getxVar());
			fileLogger.debug(" Ymean: " + yMean + " Yvar: " + accelerationPacket.getyVar());
			fileLogger.debug(" Zmean: " + zMean + " Zvar: " + accelerationPacket.getzVar());
			
			
		}
		
		
	}


	private double computeVar(double mean, Vector<Integer> AccVector) {
		
		//fileLogger.debug("Variance Accelerations Packets: " + AccVector.size());
		//fileLogger.debug(AccVector);
		
		double deviation, variance = 0;
		
		for(Integer i : AccVector){
			deviation = i - mean;
			variance += Math.pow(deviation,2);			
		}
				
		return variance/AccVector.size();
	}


	private double computeAverage(Vector<Integer> AccVector) {
		double sum = 0;
		for(Integer i : AccVector){
			sum += i;			
		}
		
		return sum/AccVector.size();
				
	}



	

}
